import { ObjectId } from "mongodb";

const SERVICE_NAME = "incidentModule";

const dateRangeLookup = {
	recent: new Date(new Date().setDate(new Date().getDate() - 2)),
	hour: new Date(new Date().setHours(new Date().getHours() - 1)),
	day: new Date(new Date().setDate(new Date().getDate() - 1)),
	week: new Date(new Date().setDate(new Date().getDate() - 7)),
	month: new Date(new Date().setMonth(new Date().getMonth() - 1)),
	all: undefined,
};

class IncidentModule {
	constructor({ logger, Incident, Monitor, User }) {
		this.logger = logger;
		this.Incident = Incident;
		this.Monitor = Monitor;
		this.User = User;
	}

	createIncident = async (incidentData) => {
		try {
			const incident = await this.Incident.create(incidentData);
			return incident;
		} catch (error) {
			error.service = SERVICE_NAME;
			error.method = "createIncident";
			throw error;
		}
	};

	createIncidents = async (incidents) => {
		try {
			await this.Incident.insertMany(incidents, { ordered: false });
		} catch (error) {
			error.service = SERVICE_NAME;
			error.method = "createIncidents";
			throw error;
		}
	};

	getActiveIncidentByMonitor = async (monitorId) => {
		try {
			return await this.Incident.findOne({ monitorId: new ObjectId(monitorId), status: true });
		} catch (error) {
			error.service = SERVICE_NAME;
			error.method = "getActiveIncidentByMonitor";
			throw error;
		}
	};

	getActiveIncidentsByMonitors = async (monitorIds) => {
		try {
			if (!monitorIds || monitorIds.length === 0) {
				return new Map();
			}

			const objectIds = monitorIds.map((id) => new ObjectId(id));
			const incidents = await this.Incident.find({
				monitorId: { $in: objectIds },
				status: true,
			});

			const map = new Map();
			incidents.forEach((incident) => {
				const monitorIdStr = incident.monitorId.toString();
				map.set(monitorIdStr, incident);
			});

			return map;
		} catch (error) {
			error.service = SERVICE_NAME;
			error.method = "getActiveIncidentsByMonitors";
			throw error;
		}
	};

	getLastManuallyResolvedIncident = async (monitorId) => {
		try {
			return await this.Incident.findOne({
				monitorId: new ObjectId(monitorId),
				status: false,
				resolutionType: "manual",
			})
				.sort({ endTime: -1 })
				.limit(1);
		} catch (error) {
			error.service = SERVICE_NAME;
			error.method = "getLastManuallyResolvedIncident";
			throw error;
		}
	};

	getIncidentById = async (incidentId) => {
		try {
			return await this.Incident.findById(incidentId).populate("monitorId", "name type url").populate("resolvedBy", "name email");
		} catch (error) {
			error.service = SERVICE_NAME;
			error.method = "getIncidentById";
			throw error;
		}
	};

	resolveIncident = async (incidentId, { resolutionType = "automatic", resolvedBy = null, comment = null, endTime = new Date() } = {}) => {
		try {
			const update = {
				status: false,
				endTime,
				resolutionType,
				resolvedBy,
				...(comment !== null && { comment }),
			};
			const incident = await this.Incident.findOneAndUpdate({ _id: new ObjectId(incidentId) }, { $set: update }, { new: true });

			if (!incident) {
				throw new Error("Incident not found");
			}

			return incident;
		} catch (error) {
			error.service = SERVICE_NAME;
			error.method = "resolveIncident";
			throw error;
		}
	};

	addCheckToIncident = async (incidentId, checkId) => {
		try {
			const incident = await this.Incident.findOneAndUpdate(
				{ _id: new ObjectId(incidentId) },
				{ $addToSet: { checks: new ObjectId(checkId) } },
				{ new: true }
			);

			if (!incident) {
				throw new Error("Incident not found");
			}

			return incident;
		} catch (error) {
			error.service = SERVICE_NAME;
			error.method = "addCheckToIncident";
			throw error;
		}
	};

	addChecksToIncidentsBatch = async (operations) => {
		try {
			if (!operations || operations.length === 0) {
				return;
			}

			const bulkOps = operations.map((op) => ({
				updateOne: {
					filter: { _id: new ObjectId(op.incidentId) },
					update: { $addToSet: { checks: new ObjectId(op.checkId) } },
				},
			}));

			await this.Incident.bulkWrite(bulkOps, { ordered: false });
		} catch (error) {
			error.service = SERVICE_NAME;
			error.method = "addChecksToIncidentsBatch";
			throw error;
		}
	};

	getIncidentsByTeam = async ({ teamId, sortOrder, dateRange, page, rowsPerPage, status, monitorId, resolutionType }) => {
		try {
			page = Number.isFinite(parseInt(page)) ? parseInt(page) : 0;
			rowsPerPage = Number.isFinite(parseInt(rowsPerPage)) ? parseInt(rowsPerPage) : 20;

			let statusBoolean = undefined;
			if (status !== undefined && status !== null) {
				if (typeof status === "string") {
					statusBoolean = status === "true";
				} else if (typeof status === "boolean") {
					statusBoolean = status;
				}
			}

			const matchStage = {
				teamId: new ObjectId(teamId),
				...(statusBoolean !== undefined && { status: statusBoolean }),
				...(monitorId && { monitorId: new ObjectId(monitorId) }),
				...(resolutionType && { resolutionType }),
				...(dateRangeLookup[dateRange] && {
					startTime: {
						$gte: dateRangeLookup[dateRange],
					},
				}),
			};

			sortOrder = sortOrder === "asc" ? 1 : -1;

			const skip = page * rowsPerPage;

			const incidents = await this.Incident.aggregate([
				{ $match: matchStage },
				{ $sort: { startTime: sortOrder } },
				{
					$facet: {
						summary: [{ $count: "incidentsCount" }],
						incidents: [{ $skip: skip }, { $limit: rowsPerPage }],
					},
				},
				{
					$project: {
						incidentsCount: {
							$ifNull: [{ $arrayElemAt: ["$summary.incidentsCount", 0] }, 0],
						},
						incidents: {
							$ifNull: ["$incidents", []],
						},
					},
				},
			]);

			return incidents[0];
		} catch (error) {
			error.service = SERVICE_NAME;
			error.method = "getIncidentsByTeam";
			throw error;
		}
	};

	getIncidentSummary = async ({ teamId, dateRange }) => {
		try {
			const matchStage = {
				teamId: new ObjectId(teamId),
				...(dateRangeLookup[dateRange] && {
					startTime: {
						$gte: dateRangeLookup[dateRange],
					},
				}),
			};

			const summary = await this.Incident.aggregate([
				{ $match: matchStage },
				{
					$group: {
						_id: "$status",
						count: { $sum: 1 },
						manualResolutions: {
							$sum: {
								$cond: [{ $eq: ["$resolutionType", "manual"] }, 1, 0],
							},
						},
					},
				},
			]);

			const result = {
				total: 0,
				active: 0,
				resolved: 0,
				manual: 0,
			};

			summary.forEach((item) => {
				result.total += item.count;
				if (item._id === true) {
					result.active = item.count;
				}
				if (item._id === false) {
					result.resolved = item.count;
				}
				result.manual += item.manualResolutions;
			});

			return result;
		} catch (error) {
			error.service = SERVICE_NAME;
			error.method = "getIncidentSummary";
			throw error;
		}
	};

	deleteIncidentsByMonitor = async (monitorId) => {
		try {
			const result = await this.Incident.deleteMany({ monitorId: new ObjectId(monitorId) });
			return result.deletedCount;
		} catch (error) {
			error.service = SERVICE_NAME;
			error.method = "deleteIncidentsByMonitor";
			throw error;
		}
	};

	deleteIncidentsByTeamId = async (teamId) => {
		try {
			const teamMonitors = await this.Monitor.find({ teamId }, { _id: 1 });
			const monitorIds = teamMonitors.map((monitor) => monitor._id);
			const deleteResult = await this.Incident.deleteMany({ monitorId: { $in: monitorIds } });
			return deleteResult.deletedCount;
		} catch (error) {
			error.service = SERVICE_NAME;
			error.method = "deleteIncidentsByTeamId";
			throw error;
		}
	};
}

export default IncidentModule;
